from functools import partial
from sys import stderr
import os

from utils.namespace import Namespace
from utils.misc import INF

from stacking.instance.generation import SimGen
from stacking.warehouse import Warehouse
from stacking.instance import Instance
from stacking.solvers import HeuristicSolver, heuristics


def generate_benchmark(generator, param_list, group_size=10, base_dir=".", 
                       validate_params_fnc=None, skip_found=True):
    required_params = set(["nitems", "nstacks", "target", "epsilon"])
    provided_params = set(param for param, _ in param_list)
    if not provided_params >= required_params:
        raise Exception("missing parameters - %s" % (required_params - provided_params))
        
    base_dir  = affirm_dir(os.path.abspath(base_dir), type(generator).__name__)
    separator = ("." * 100) + "\n"
    
    for params in param_combinations(param_list):
        group_id = "i%d_s%d_t%s" % (params.nitems, params.nstacks, params.target)
        if validate_params_fnc is not None and not validate_params_fnc(params):
            continue
        log(separator)
        log("Instance group %s. Parameter values:\n" % group_id)
        for k, v in params.iteritems():
            log("%20s = %s\n" % (k, v))
            
        group_dir = affirm_dir(base_dir, group_id)
        for seed in xrange(group_size):
            filename = "instance%02d.sp" % seed
            filepath = os.path.join(group_dir, filename)
            failed_filename = "failed_" + filename
            failed_filepath = os.path.join(group_dir, failed_filename)
            if skip_found and (os.path.isfile(filepath) or os.path.isfile(failed_filepath)):
                log(" ... skipping %s (file already exists)\n" % filename)
                continue
                
            log(" ... generating %s ... " % filename)
            best = generator.generate(seed=seed, max_iterations=50, max_cpu=INF, **params)
            if best.accepted:
                log("success.\n")
            else:
                log("failed.\n")
                filepath = failed_filepath
            
            items = best.pop("items")
            warehouse = Warehouse.quick_build(id="%s#%s" % (group_id, filename), 
                                              nstacks=params.nstacks)
            instance = Instance(items, warehouse)
            instance.meta.update((k, v) for k, v in params.iteritems() if not callable(v))
            instance.meta.update(best)
            
            log("     saving to %s ... " % filepath)
            instance.save(filepath)
            log("done.\n     instance metadata:\n")
            for k, v in instance.meta.iteritems():
                log("%40s = %s\n" % (k, v))
    log(separator)
    log("Finished generating benchmark %s." % base_dir)
    
    
def solve_cm(instance):
    """Solve a given instance with the Conflict Minimization placement heuristic."""
    solver = HeuristicSolver(heuristics.CM)
    solver.verbose = False
    solver.solve(instance, seed=0)  # seed is always the same to make results reproducible
    return solver.solution.meta.relocations
    
    
def config_generator(generator):
    """Adjust the generator's production interval before the start of its main loop. The larger the 
    target "difficulty" value, the smaller the initial production interval."""
    generator.production_interval /= generator.target
    
    
nr_benchmark_params = [("nitems", [100, 1000, 10000]), 
                       ("nstacks", [20]), 
                       ("target", [1.0, 2.0, 3.0, 4.0, 5.0]),
                       ("epsilon", [0.5]),
                       ("solve_fnc", [solve_cm]),
                       ("config_fnc", [config_generator])]
ips_benchmark_params = [("nitems", [100, 1000, 10000]), 
                        ("nstacks", [20]), 
                        ("target", [5.0, 10.0, 15.0, 20.0, 25.0]),
                        ("epsilon", [1.0]),
                        ("config_fnc", [config_generator])]

def ips_validate_params(params):
    return float(params.nitems) / params.nstacks >= params.target - params.epsilon
    
    
generate_nr  = partial(generate_benchmark, SimGen.NR(), nr_benchmark_params)
generate_ips = partial(generate_benchmark, SimGen.IPS(), ips_benchmark_params, 
                       validate_params_fnc=ips_validate_params)


